package com.flyqiu.flow.core.tools;

import com.flyqiu.flow.bpmn.base.BaseProcElementNode;
import com.flyqiu.flow.bpmn.element.SequenceFlow;
import com.flyqiu.flow.core.fliter.BpmnPathFilter;
import lombok.Getter;
import com.flyqiu.flow.bpmn.element.BaseProcElement;


import java.util.*;
import java.util.stream.Collectors;

/**
 * 路径计算器
 */
public class BpmnPathSearch {


    @Getter
    private final BaseProcElement startElement;
    @Getter
    private final BaseProcElement endElement;

    private final Set<String> pathKey;

    private final LinkedHashMap<String, List<BaseProcElement>> bpmnPathMap;

    private BpmnPathFilter pathFilter;
    private final DispatchPathFilter dispatchPathFilter = new DispatchPathFilter();

    public void setPathFilter(BpmnPathFilter pathFilter) {
        this.pathFilter = pathFilter;
    }

    public BpmnPathSearch(BaseProcElement startElement, BaseProcElement endElement) {

        if (startElement == null) {
            throw new RuntimeException("startElement cannot be null or empty");
        }
        if (endElement == null) {
            throw new RuntimeException("endElement cannot be null or empty");
        }
        this.startElement = startElement;
        this.endElement = endElement;
        pathKey = new HashSet<>();
        bpmnPathMap = new LinkedHashMap<>();
    }

    /**
     * 查找路径
     *
     * @return 查找到的路径
     */
    public LinkedHashMap<String, List<BaseProcElement>> search() {
        this.pathKey.clear();
        bpmnPathMap.clear();
        Map<String, List<BaseProcElement>> pathMap = search(new LinkedHashSet<>(), startElement);
        if (pathMap == null || pathMap.isEmpty()) return bpmnPathMap;
        Set<Map.Entry<String, List<BaseProcElement>>> entries = pathMap.entrySet();
        for (Map.Entry<String, List<BaseProcElement>> entry : entries) {
            List<BaseProcElement> path = entry.getValue();
            Collections.reverse(path);
            String key = entry.getValue().stream().map(BaseProcElement::getDefKey).collect(Collectors.joining("->"));
            boolean apply = dispatchPathFilter.filter(key, path);//路径事件通知
            if (!apply) {
                continue;
            }
            bpmnPathMap.put(key, path);
        }
        this.pathKey.clear();
        return bpmnPathMap;
    }

    /**
     * 查找路径
     *
     * @param pathKeys
     * @param element
     * @return
     */
    private Map<String, List<BaseProcElement>> search(LinkedHashSet<String> pathKeys, BaseProcElement element) {
        if (element == null) {
            return null;
        }
        if (pathKeys.contains(element.getDefKey())) {
            return null;
        }
        pathKeys.add(element.getDefKey());
        String uniqueKey = String.join(",", pathKeys);
//        System.out.println("path:" + uniqueKey);
        if (pathKey.contains(uniqueKey)) {
            pathKeys.remove(element.getDefKey());
            return null;
        }
        pathKey.add(uniqueKey);
        Map<String, List<BaseProcElement>> result = new LinkedHashMap<>();
        if (endElement.getDefKey().equals(element.getDefKey())) {
            List<BaseProcElement> temp = new ArrayList<>();
            temp.add(endElement);
            result.put(element.getDefKey(), temp);
            pathKeys.remove(element.getDefKey());
            return result;
        }
        if (element instanceof SequenceFlow sequenceFlow) {
            BaseProcElement targetNode = sequenceFlow.getTargetNode();
            Map<String, List<BaseProcElement>> pathMap = search(pathKeys, targetNode);
            pathKeys.remove(element.getDefKey());
            return mergeChildPath(element, result, pathMap);
        }
        assert element instanceof BaseProcElementNode;
        BaseProcElementNode startNode = (BaseProcElementNode) element;
        List<SequenceFlow> flows = startNode.getOutgoingFlows();
        for (SequenceFlow flow : flows) {
            Map<String, List<BaseProcElement>> childPath = search(pathKeys, flow);
            mergeChildPath(element, result, childPath);
        }
        pathKeys.remove(element.getDefKey());
        return result;
    }

    private Map<String, List<BaseProcElement>> mergeChildPath(BaseProcElement element, Map<String, List<BaseProcElement>> elementPathMap, Map<String, List<BaseProcElement>> childPath) {
        if (childPath == null || childPath.isEmpty()) {
            return elementPathMap;
        }
        Map<String, List<BaseProcElement>> result = elementPathMap;
        if (result == null) {
            result = new LinkedHashMap<>();
        }
        Set<Map.Entry<String, List<BaseProcElement>>> entries = childPath.entrySet();
        for (Map.Entry<String, List<BaseProcElement>> item : entries) {
            String key = item.getKey();
            List<BaseProcElement> value = item.getValue();
            value.add(element);
            result.put(element.getDefKey() + "," + key, item.getValue());
        }
        return result;
    }


    private class DispatchPathFilter implements BpmnPathFilter {
        @Override
        public boolean filter(String pathName, List<BaseProcElement> path) {
            if (pathFilter == null) return true;
            return pathFilter.filter(pathName, path);
        }
    }

}
